/*
 ============================================================================
 Name        : tp0_6620.c
 Author      : Agustin Pivetta, Diego Rivas
 Version     : 1.0
 Description : Tp0 66.20: Dump Octal
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <getopt.h>

#define MIN_NUM_ASCII 48
#define MAX_NUM_ASCII 57
#define NEW_LINE '\n'

/*
 * Convierto un byte a byte octal
 */
void bin2oc(unsigned char bin, char octal[]) {
	unsigned int dividendo;
	unsigned char resto;
	unsigned int cociente;
	int i;
	dividendo = bin;
	// Repito solo 3 veces porque ya sé que me va a venir un numero entre 
    // 0 y 255 (como mucho 3 bytes octales)
	for (i = 2; i >= 0; i--) {
		cociente = dividendo / 8;
		resto = dividendo % 8;
		octal[i] = resto;
		dividendo = cociente;
	}
}

/*
 * convierto un byte octal a byte
 */
unsigned char oc2bin(char octal[]) {
	unsigned char bin = 0;
	unsigned char digito;
	unsigned int base;
	int i = 0;
	int j = 0;

	for (i = 0; i < 3; i++) {
		//ASCII -> numero
		switch (octal[i]) {
		case 48: //0
			digito = 0;
			break;
		case 49: //1
			digito = 1;
			break;
		case 50: //2
			digito = 2;
			break;
		case 51: //3
			digito = 3;
			break;
		case 52: //4
			digito = 4;
			break;
		case 53: //5
			digito = 5;
			break;
		case 54: //6
			digito = 6;
			break;
		case 55: //7
			digito = 7;
			break;
		case 56: //8
			digito = 8;
			break;
		case 57: //9
			digito = 9;
			break;
		default:
			fprintf(stderr, "Error, el caracter leido no es un valor de la base octal\n");
			exit(-1);
			break;
		}

		// Calcula la base con la que se tiene q multiplicar el 
        // digito 8^posicion
		base = 1;
		for (j = i; j < 2; j++) {
			base *= 8;
		}

		bin += digito * base;
	}

	return bin;
}

/*
 * Imprimo la ayuda
 */
void printHelp() {
	printf("Usage:\n"
"  tp0 -h\n"
"  tp0 -V\n"
"  tp0 [options]\n"
"Options:\n"
"  -h, --help      Print this information.\n"
"  -V, --version   Print version and quit.\n"
"  -O, --octal     Octal dump mode: encode the input (binary file) in \n"
"                  octal format (default).\n"
"  -b, --binary    Binary mode: decode the input (octal dump file) in\n"
"                  binary format.\n"
"  -i, --input     Path to input file (defaults to stdin).\n"
"  -o, --output    Path to output file (defaults to stdout).\n"
"  -l, --length    Number of octal bytes per line (line length) at the\n"
"                  output in octal dump mode (0 = default = no limit).\n"
"  -d, --delimiter Delimiter between octal bytes. Could be a string, not\n"
"                  only a single byte. Used as delimiter for octal output\n"
"                  and input files (default: none).\n");
        
	
}

/*
 * Imprimo la version
 */
void printVersion() {
	printf("66.20 - TP0: Octal Dump, Versión 1.00\n");;
	return;
}
/*
 * Parseo number y si es un numero entero valido lo casteo a entero
 */
size_t parseInt(const char* number) {
	char len = strlen(number);

	// Si me envían un cero devuelvo 0
	if( (len == 1) && (number[0] == '0') ) return 0;

	size_t i=0;
	// Recorro a m y verifico que cada uno de sus caracteres sean números
	for(i=0; i < len; i++) {
		if ( (number[i] < MIN_NUM_ASCII) || (number[i] > MAX_NUM_ASCII) ) 
		return 0;
	}       

	// Si m es una cadena válida la convierto en un número
	unsigned int aux = 0;
	sscanf(number, "%u", &aux);
	return aux;
}

void checkIOError(FILE* file2check, char* error_msg, FILE* otherFile){
    if (ferror(file2check)){
        // si fallo escribo por stderr el mensaje
        fprintf(stderr, "%s", error_msg);
        
        // si los archivos sobre los que estoy escribiendo no son stdin ni stdout
        // los cierro antes de salir
        if (file2check != stdin && file2check != stdout){
            fclose(file2check);
        }
        
        if (otherFile != stdin && otherFile != stdout){
            fclose(otherFile);
        }
        
        // retorno el codigo de error
        exit(-1);
    }
}

/* 
 * Leo input, genero un volcado octal y lo escribo en output con length
 * bytes octales por linea con delimitador delimiter
 */
void octalDump(FILE* input, FILE* output, size_t length, char* delimiter){
	unsigned char c;
	char octal[3];
	size_t cont = 0;
    bool first = true;
	
	c = fgetc(input);
    checkIOError(input, "Se produjo un error al leer del archivo de entrada", output);
    
	while (!feof(input)){
		if (cont == length && length != 0){
			fprintf(output, "%c", NEW_LINE);
            checkIOError(output, "Se produjo un error al escribir en el archivo de salida", input);
           
			cont = 0;
		}else if (delimiter != NULL && first != true){
			fprintf(output, "%s", delimiter);
            checkIOError(output, "Se produjo un error al escribir en el archivo de salida", input);
		}
        
        
		bin2oc(c, octal);
		fprintf(output, "%i%i%i", octal[0], octal[1], octal[2]);
        checkIOError(output, "Se produjo un error al escribir en el archivo de salida", input);
        
		cont++;
		
        first = false;
		
		c = fgetc(input);
	}
}

/* 
 * Leo input con length bytes octales por linea con delimitador delimiter, 
 * genero un volcado binario y lo escribo en output 
 */
void binaryDump(FILE* input, FILE* output, size_t length, char* delimiter){
	unsigned char bin;
	char octal[3];
	
	size_t cont = 0;
	size_t i;
	
	fread(octal, sizeof(char), 3, input);
    checkIOError(input, "Se produjo un error al leer del archivo de entrada", output);
    
	while (!feof(input)){
		cont++;
		
		bin = oc2bin(octal);
		fprintf(output, "%c", bin);
        checkIOError(output, "Se produjo un error al escribir en el archivo de salida", input);
        
		if (length > 0 && cont == length){
			// si llegue a la cantidad de bytes octales por linea indicada 
            // por parametro, ignoro todo lo que sigue hasta el proximo 
            // salto de linealeo hasta el '\n'
			cont = 0;
			while (!feof(input) && fgetc(input) != NEW_LINE){
                checkIOError(input, "Se produjo un error al leer del archivo de entrada", output);
            }
		}else if (delimiter != NULL){
			//leo la cantidad de caracteres del delimitador
			for (i = 0; i < strlen(delimiter); i++){
				if (!feof(input)){
					fgetc(input);
                    checkIOError(input, "Se produjo un error al leer del archivo de entrada", output);
				}else{
					break;
				}
			}
		}
		
		fread(octal, sizeof(char), 3, input);
        checkIOError(input, "Se produjo un error al leer del archivo de entrada", output);
	}
}

/*
 * Abro los archivos de input/outout
 */
FILE* openFile(char* fileName, FILE* default_file, const char* mode){
	FILE* fd;
	if (fileName == NULL){
		fd = default_file;
	}else{
		fd = fopen(fileName, mode);
	}
	
	if (!fd){
		fprintf(stderr, "Error al intentar abrir el archivo");
		exit(-1);
	}
	return fd;
}

int main(int argc, char** argv) {
	// Fuerzo que un error en getopt no se imprima por stderr
	opterr = 0;
	// Indica cuando terminé de procesar todos los argumentos
	int nextOpt = 0;
	// Cadena que lista las opciones cortas válidas
	const char* const opCortas = "hVObi:o:l:d:";
	// Estructura de structs describiendo los valores largos
	const struct option opLargas[] = {
			{"help", no_argument, NULL, 'h'},
			{"version", no_argument, NULL, 'V'},
			{"octal", no_argument, NULL, 'O'},
			{"binary", no_argument, NULL, 'b'},
			{"input", optional_argument, NULL, 'i'},
			{"output", optional_argument, NULL, 'o'},
			{"length", optional_argument, NULL, 'l'},
			{"delimiter", optional_argument, NULL, 'd'},
			{NULL, no_argument, NULL, 0}
	};

	
	// Variable donde recibo el nombre del archivo de entrada
	char* inputFName = NULL;
	// Variable donde recibo el nombre del archivo de salida
	char* outputFName = NULL;
	// Variable donde guardo el modo de ejecucion (1:octal dump mode | 2: binary dump mode)
	char mode = 1;
	// Variable donde guardo la longitud de numeros octal por linea
	size_t length = 0;
	// Variable donde guardo el delimitador de bytes octales
	char* delimiter = NULL;

	while (1) {
		//Leo que me devuelve get_opt
		nextOpt = getopt_long(argc, argv, opCortas, opLargas, NULL);

		if (nextOpt == -1) {
			break;
		}
		
		switch(nextOpt) {
			case 'h': {
				printHelp();
                return 0;
				break;
			}
			
			case 'V': {
				printVersion();
                return 0;
				break;
			}

			case 'O': {
				mode = 1;
				break;
			}
			
			case 'b': {
				mode = 2;
				break;
			}       
			
			case 'i':{
				if (strcmp(optarg, "-") != 0){
					inputFName = malloc(strlen(optarg)+1);
					strcpy(inputFName, optarg);
				}
				break;
			}
			
			case 'o':{
				if (strcmp(optarg, "-") != 0){
					outputFName = malloc(strlen(optarg)+1);
					strcpy(outputFName, optarg);
				}
				break;
			}
			
			case 'l':{
				length = parseInt(optarg);
				break;
			}
			
			case 'd':{				
				delimiter = malloc(strlen(optarg)+1);
				strcpy(delimiter, optarg);
				break;
			}

			default: {
				printHelp();
				exit(-1);
				break;
			}
		}                
	}
	
	FILE* input = openFile(inputFName, stdin, "rb");
	FILE* output = openFile(outputFName, stdout, "wb");
	
	if (mode == 1){
		octalDump(input, output, length, delimiter);
	}else if (mode == 2){
		binaryDump(input, output, length, delimiter);
	}
	
	fclose(input);
	fclose(output);
	
	return 0;
}
